using System;
using System.Collections.Generic;
using System.Text;

using Mono.Cecil;
using Mono.Cecil.Cil;

namespace CILFlow
{
    public class CILPair
    {
        public Instruction Source;
        public Instruction Sink;
        public PairEntity SourceEntity;
        public PairEntity SinkEntity;
        public string Relationship;

        public string Name
        {
            get
            {
                return this.Source.OpCode.Name + ":::" +
                    (this.SourceEntity == null ? "" : this.SourceEntity.Name) +
                        Relationship +
                this.Sink.OpCode.Name   + ":::" +
                    (this.SinkEntity   == null ? "" : this.SinkEntity.Name);
            }
        }

        public override bool Equals(object obj)
        {
            CILPair other = (CILPair)obj;
            if (this.Source.OpCode == other.Source.OpCode &&
                this.Source.Operand == other.Source.Operand &&
                this.Sink.OpCode == other.Sink.OpCode &&
                this.Sink.Operand == other.Sink.Operand)
            {
                return true;
            }
            return false;
        }
        public override int GetHashCode()
        {
            return this.Source.GetHashCode() ^
                   this.Sink.GetHashCode();
        }


        public static List<CILPair> ExtractPairs(MethodDefinition md)
        {
            CILFlowGraph graph = CILMethodBlockConstructor.ConstructFlow(md);

            List<CILPair> pairs = new List<CILPair>();

            foreach (CILInstructionBlock block in graph.Blocks)
            {
                foreach (PopTree tree in PopTreeListBuilder.Construct(md, block).PopTrees)
                {
                    // Lost of information: Parent blocks.
                    pairs.AddRange( ConstructFlow(md, tree) );
                }
            }
            return pairs;
        }

        public static List<CILPair> ConstructFlow(MethodDefinition md, PopTree tree)
        {
            List<CILPair> flows = new List<CILPair>();
            ConstructFlow(md, tree, flows);
            return flows;
        }
        private static void ConstructFlow(MethodDefinition md, PopTree tree, List<CILPair> flows)
        {
            foreach (PopTree child in tree._PopList)
            {
                CILPair pair = BuildPair(child, tree,md);
                //object childEntity = Entity.GetEntity(md, child._instruction);
                //object parentEntity = Entity.GetEntity(md, tree._instruction);
                //if (childEntity != null && parentEntity != null)
                {
                    //new Flow(md, child._instruction, tree._instruction)
                    //flows.Add();
                }
                flows.Add(pair);
                // Send the pain below.
                ConstructFlow(md, child, flows);
            }
        }

        private static CILPair BuildPair(PopTree child, PopTree parent,MethodDefinition md)
        {
            CILPair pair = new CILPair();
            pair.Source = child._instruction;
            pair.Sink = parent._instruction;
            pair.SinkEntity = PairEntity.ConstructPairEntity(child._instruction,md);
            pair.SourceEntity = PairEntity.ConstructPairEntity(parent._instruction, md);
            pair.Relationship = "";
            return pair;
        }
    }

    public class PairEntity
    {
        public object CILType;
        public string Name;
        public string Location;
        public string Type;
        public string EntityType;

        public static PairEntity ConstructPairEntity(Instruction i, MethodDefinition md)
        {
            PairEntity pairentity = new PairEntity();
            object entity = CILUtil.Entity.GetEntity(md, i);
            if (entity != null)
            {
                pairentity.CILType = entity;
                
                IMemberReference member = entity as IMemberReference;
                if (member != null)
                {
                    pairentity.Name = member.Name;
                    // Aggressive expansion?
                    pairentity.Location = member.DeclaringType == null ? member.Name : member.DeclaringType.FullName;
                    pairentity.Type = "";
                    pairentity.EntityType = "member";
                    return pairentity;
                }
                
                VariableReference var = entity as VariableDefinition;
                if( var != null )
                {
                    pairentity.Name = var.Name;
                    pairentity.Location = md.DeclaringType.FullName + "." + md.Name;
                    pairentity.Type = var.VariableType.FullName;
                    pairentity.EntityType = "variable";
                    return pairentity;
                }

                ParameterReference par = entity as ParameterReference;
                if (par != null)
                {
                    pairentity.Name = par.Name;
                    pairentity.Location = md.DeclaringType.FullName + "." + md.Name;
                    pairentity.Type = par.ParameterType == null ? "this" : par.ParameterType.FullName;
                    pairentity.EntityType = "parameter";
                    return pairentity;
                }

                pairentity.Name = "";
                pairentity.Location = "";
                pairentity.Type = "";
                pairentity.EntityType = "unknown";
            }
            return null;
        }
    }
}
